<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\orm\annotations;

#use \BadMethodCallException;
#use \InvalidArgumentException;
#use \Reflector;
#use \ReflectionProperty;
#use \ReflectionMethod;
#use \ezcPersistentObjectDefinition;
#use tehframework\Inflector;
#use tehframework\annotations\Annotation;
#use tehframework\annotations\AnnotationTargetAware;

abstract class RelationAnnotation implements Annotation, AnnotationTargetAware
{
	/**
	 * @var ReflectionClass
	 */
	protected $target;
	
	/**
	 * @var string?
	 */
	protected $entity;
	
	/**
	 * @var string?
	 */
	protected $name;
	
	/**
	 * @var string?
	 */
	protected $sourceTable;
	
	/**
	 * @var string?
	 */
	protected $destinationTable;
	
	/**
	 * @var array
	 */
	protected $columnMap;
	
	/**
	 * @var bool
	 */
	protected $reverse = false;
	
	/**
	 * @param  string? $entity
	 */
	public function __construct($entity = null)
	{
		$this->entity = empty($entity) ? null : $entity;;
	}
	
	/**
	 * @param  string $name
	 * @return RelationAnnotation
	 */
	public function setName($name)
	{
		$this->name = empty($name) ? null : $name;
		
		return $this;
	}
	
	/**
	 * @param  string $sourceTable
	 * @return RelationAnnotation
	 */
	public function setSourceTable($sourceTable)
	{
		$this->sourceTable = empty($sourceTable) ? null : $sourceTable;
		
		return $this;
	}
	
	/**
	 * @param  string $destinationTable
	 * @return RelationAnnotation
	 */
	public function setDestinationTable($destinationTable)
	{
		$this->destinationTable = empty($destinationTable)
			? null : $destinationTable;
		
		return $this;
	}
	
	/**
	 * @param  array $columnMap
	 * @return RelationAnnotation
	 */
	public function setColumnMap($columnMap)
	{
		$this->columnMap = empty($columnMap) ? array() : $columnMap;
		
		return $this;
	}
	
	/**
	 * @param  bool $reverse
	 * @return RelationAnnotation
	 */
	public function setReverse($reverse)
	{
		$this->reverse = (bool)$reverse;
		
		return $this;
	}
	
	/**
	 * @param  ezcPersistentObjectDefinition $def
	 * @param  array<string,ezcPersistentObjectDefinition> $defs
	 * @throws InvalidArgumentException
	 * @throws BadMethodCallException
	 */
	public function applyDefinition(
		ezcPersistentObjectDefinition $def, array $defs)
	{
		if (!empty($this->entity) && !isset($defs[$this->entity]))
		{
			throw new InvalidArgumentException(sprintf(
				'Can not apply [%s] relation to [%s] definition, ' .
				'because a definition of the related entity is not present: [%s].',
				$this->createName(), $def->class, $this->entity
			));
		}
		
		$def->relations[$this->createName()] = $this->createRelation($def, $defs);
	}
	
	/**
	 * @return string
	 */
	protected function createName()
	{
		if (!empty($this->name))
		{
			return $this->name;
		}
		
		if (!empty($this->entity))
		{
			return $this->entity;
		}
		
		return substr($this->target->name, 0, 3) === 'set'
			? substr($this->target->name, 3) : $this->target->name;
	}
	
	/**
	 * @param  ezcPersistentObjectDefinition $def
	 * @return string
	 */
	protected function createSourceTable(ezcPersistentObjectDefinition $def)
	{
		return empty($this->sourceTable) ? $def->table : $this->sourceTable;
	}
	
	/**
	 * @param  array<string,ezcPersistentObjectDefinition> $defs
	 * @return string
	 * @throws InvalidArgumentException
	 */
	protected function createDestinationTable(array $defs)
	{
		if (!empty($this->destinationTable))
		{
			return $this->destinationTable;
		}
		
		if (empty($this->entity))
		{
			throw new InvalidArgumentException(
				'Destination table can not be empty, if entity was not specified.'
			);
		}
		
		return $defs[$this->entity]->table;
	}
	
	/**
	 * @param  ezcPersistentObjectDefinition $def
	 * @return string
	 */
	protected function createDestinationColumn(ezcPersistentObjectDefinition $def)
	{
		return Inflector::underscore(
			$def->class . ucfirst($def->idProperty->propertyName)
		);
	}
	
	/**
	 * @param  ezcPersistentObjectDefinition $def
	 * @param  array<string,ezcPersistentObjectDefinition> $defs
	 * @return ezcPersistentRelation
	 * @throws BadMethodCallException
	 */
	abstract protected function createRelation(
		ezcPersistentObjectDefinition $def, array $defs);
	
	#region AnnotationTargetAware
	
	/**
	 * @param  Reflector $annotationTarget
	 * @throws BadMethodCallException
	 */
	public function setAnnotationTarget(Reflector $annotationTarget)
	{
		if (!($annotationTarget instanceof ReflectionProperty)
		&& !($annotationTarget instanceof ReflectionMethod))
		{
			throw new BadMethodCallException();
		}
		
		$this->target = $annotationTarget;
	}
	
	#endregion
	
	#region Annotation
	
	/**
	 * Returns a bitwise flag of targets which can be annotated
	 * with this annotation.
	 * 
	 * @return int
	 *         A bitwise flag of property or method targets. 
	 */
	public function getAnnotationTargets()
	{
		return Annotation::TARGET_PROPERTY | Annotation::TARGET_METHOD;
	}
	
	/**
	 * Determines whether annotation can be used multiple times
	 * on a single target.
	 * 
	 * @return bool
	 *         Always FALSE. This annotation can not be used multiple times on
	 *         single target.
	 */
	public function allowsMultiple()
	{
		return false;
	}
	
	#endregion
}